#!/usr/bin/env python
# -*- coding: utf-8 -*-
#coding=utf8

import sys, getopt
import traceback
import re
import os

is3X = False
try:
    type(long)
except:
    is3X = True
    long = int

'''	csbuf 的协议解析器
	对协议文件进行词法分析、语法分析产生结构树，结构树如下：
	proto = {
		"package"	: packageName,
		"import"	: [protofiles, ],
		"messages"	: [message, ],
		"importCount"	: 0,	# 被引用的次数
	}
	消息结构字段定义 {数据类型, 类型所在的包名(针对自定义的message类型)， 变量名, 序号}，其中数据类型可以是其它消息名
	message = {
		"name" : msgName,
		"protoid" : protoid,	# 协议 id
		"fields" : ("kind": KIND, "package": "" "id": ID, "pos": DIGIT, "Kind": KIND.capitalize(), "Id", ID.capitalize(), "array": false, "group": 0, "filename": filename),
		"filename": filename,
	}
'''

_Protos = {}		# 协议结构表，以文件名作为key，该文件内所生成的协议结构作为 value
_Messages = {}		# 消息名字-位置表，用来验证是否已经有同名消息了
_ProtoIds = {}		# 协议号-位置表，用来验证是否已经有同名协议了

_MsgPackage = {}	# 消息名和包名的映射表
_MsgFilename = {}	# 消息名和定义文件名的映射表

# 保留字
_keyWorlds = ["package", "import", "message", "array", "group"]

# 类型名
_kindWorlds = ["bool", "string", "bytes", "uint8", "uint16", "uint32", "uint64", "int8", "int16", "int32", "int64", "float", "double"]

# 符号编码
_codes = {
	"ID"	: 1,		# 标识符，用来表示各种名字，如变量名，数组名，过程名等等
	"KEY" 	: 2,		# 保留字
	"KIND"	: 3,		# 数据类型
	";"		: 4,
	":"		: 5,
	"{"		: 6,
	"}"		: 7,
	"="		: 8,
	"DIGIT"	: 9,		# 整数
	"_"		: 10,		# 跳过符号
	"PATH"	: 11,		# 路径
	
	-1		: u"无法识别类型",
	-2		: u"非法字符",
}

isGo = False
isLua = False
isTs = False
isPy = False

_dirname = ""

_usage = u'''.csbuf 协议解析器
proto.py infile [-o outdir] [-c] [-l] [-g]
  infile 	可以指定协议声明文件或所在目录
  -i infile 指定协议声明文件或所在目录，当输入源多于一处时，使用这个标识添加更多的输入
  -o outdir	指定 outdir 为生成的协议文件存放目录
  -c 		指定需要生成 c++ 协议
  -l		指定需要生成 lua 协议
  -g		指定需要生成 go 协议
  -t		指定需要生成 typescript 协议
  -p		指定需要生成 python 协议
  
协议声明文件可使用的关键字有 package, import, message, array, group, map
可使用的基本数据类型有 bool, string, bytes, uint8, uint16, uint32, uint64, int8, int16, int32, int64, float, double
协议定义以分号作为结束符，// 用于注释，消息域可以使用基本类型或其它消息定义，需要指定所在序号
  package	声明协议所在包，必须在每个协议文件头定义，后接包名
			如 package test
  import	导入其它协议文件，后接协议声明文件名（不含后缀名）
			如 import test
  message	声明一个消息体，后接消息名和消息编号，消息域以大括号封闭
			消息编号是作为消息的识别码，如果不写则使用自动编号
			如 message mymsg 1001 { uint8 field1 = 1; }
  array		声明消息域为数组，出现在消息域头部
			如 array string pid = 2;
  group		声明一组使用相同协议号的消息组，所有不在 group 标志内的消息都分为 0 组
  			在 group 标志内的组按定义顺序分组为 1, 2 ...
  			给 group 指定的协议号会组这个标志内的消息都使用这个协议号
  			可以使用 _; 来跳过某一分组的消息定义
  			如 group 2001 { message ..., message ... }
  
以下是一个消息示例：
package test
import test1
message Test1 1001 {
	uint32 pid = 1;	// 角色 id
	array string gid = 2;	// 定义一个字符串数组
}'''

def capitalize(s):
	'''首字母大写'''
	if s == "bytes" and not isTs:
		return "String"
	return s[:1].upper() + s[1:]

def usage():
	''' 显示帮助信息 '''
	print (_usage)
	
def error(*args):
	print (" ".join(args))
	raise Exception
	
def checkEnd(token, lineno):
	''' 检查是否结束符：分号 '''
	if token[0] != _codes[";"]:
		error(lineno, u"缺少结束符;")

def checkKey(token, key, lineno):
	''' 检查是否对应的关键字 '''
	if token[0] != _codes["KEY"] or token[1] != key:
		error(lineno, u"错误的关键字", token[1], u"需要的是", key)
		
def checkSpc(token, spc, lineno):
	''' 检查是否分界符，spc是该符号 {}等 '''
	if token[0] != _codes[spc]:
		error(lineno, u"缺少符号", spc, u"该位置上是", token[1])
		
def checkSynNumber(index, sum, number, lineno):
	''' 检查从 index 开始还有没有 number 个符号 '''
	if index + number >= sum:
		error(lineno, u"意外的结束")
		
def checkVarSyn(token, lineno):
	''' 检查是否是变量符号 '''
	if token[0] != _codes["ID"]:
		error(lineno, u"缺少变量", token[1], u"该位置上是", token[1])
		
def checkFieldIndex(number, oldnumber, lineno):
	''' 检查消息域定义的序号是否正确的递增整数 '''
	if number <= 0:
		error(lineno, u"错误的消息域序号定义，%d 必须大于 0" % number)
	if number <= oldnumber:
		error(lineno, u"错误的消息域序号定义，%d 必须大于 %d" % (number, oldnumber))
		
def checkPathSyn(token, lineno):
	''' 检查是否路径符号 '''
	if token[0] != _codes["PATH"] and token[0] != _codes["ID"]:
		error(lineno, u"并不是有效的路径格式", token[1], u"该位置上是", token[1])

def has_key(d, name):
	if is3X:
		return name in d
	return d.has_key(name)
		
_CheckKinds = []	# 需要进行自定义消息类型检查的列表 (kind, errormsg)

def checkKind(token, lineno, filename):
	''' 检查是否自定义消息数据类型 '''
	#if token[0] != _codes["KIND"] and (token[0] != _codes["ID"] or not _Messages.has_key(token[1])):
	#	error(lineno, u"消息的域类型定义错误", token[1])
	if token[0] != _codes["KIND"] and token[0] == _codes["ID"]:
		#error(lineno, u"消息的域类型定义错误", token[1])
		if not has_key(_Messages, token[1]):
			_CheckKinds.append((token[1], u"%s 消息的域类型定义错误 %s" % (lineno, token[1])))
		else:
			if _MsgFilename[token[1]] != filename:
				_Protos[_MsgFilename[token[1]]]["importCount"] = _Protos[_MsgFilename[token[1]]]["importCount"] + 1
		
		
def checkKinds():
	''' 进行自定义消息类型检查，同时也在这里给 importCount 添加计数 '''
	err = 0
	hasImport = {}	# 存储已经添加过计数的文件
	for v in _CheckKinds:
		if not has_key(_Messages, v[0]):
			print (v[1])
			err += 1
		else:
			isBreak = False
			for k, p in _Protos.items():
				for msg in p["messages"]:
					if msg["name"] == v[0]:
						if not has_key(hasImport, msg["filename"]):
							hasImport[msg["filename"]] = True
							p["importCount"] = p["importCount"] + 1
						isBreak = True
						break
				if isBreak:
					break
	if err > 0:
		error(u"使用了 %d 个未定义的消息类型定义" % err)

def checkProtoId(protoid, lineno):
	''' 检查协议号是否有重复 '''
	if has_key(_ProtoIds, protoid):
		error(lineno, u"重复的协议号定义", protoid, u"已经在", _ProtoIds[protoid])
	_ProtoIds[protoid] = lineno

class Parser:
	def __init__(self):
		self.filename = ""
		self.basename = ""
		self.lineno = 1
		self.package = ""
		self.tokens = None
		self.n = 0
		
		
	def parseLine(self, line, tokens):
		''' 解析行 '''
		content = re.sub("//.*", "", line)		# 清除代码中的注释
		words = re.split("([\{\};:=])|\s+", content.strip())
		info = ""
		try:
			line = line.encode("utf-8")
		except:
			try:
				line = line.encode("gb2312")
			except:
				pass
		try:
			info = u"%s: %d %s" % (os.path.basename(self.filename), self.lineno, line)
		except:
			info = u"%s: %d %s" % (os.path.basename(self.filename), self.lineno, "")
		for w in words:
			syn = -1
			#temp = []
			
			#end = None
			#for c in reversed(w):	# 找出单词中的分号;
			#	if c == ';':
			#		end = c
			#		w = w[:-1]
			#	else:
			#		break
			#if end != None:
			#	temp.append((_codes[end], end, info))
			if w == None or w == "":
				continue
			if _keyWorlds.count(w) >= 1:	# 关键字
				syn = _codes["KEY"]
			elif _kindWorlds.count(w) >= 1:	# 数据类型名
				syn = _codes["KIND"]
			elif w.isdigit():
				syn = _codes["DIGIT"]
			elif w == ";" or w == "=" or w == "{" or w == "}" or w == "_":
				syn = _codes[w]
			else:
				sech = re.search("^[a-zA-Z\./][a-zA-Z0-9_\./]*$", w)
				if not sech:
					syn = -2
				else:
					if -1 == w.find(".") and -1 == w.find("/"):
						syn = _codes["ID"]
					else:
						syn = _codes["PATH"]
				
			if syn > 0:
				tokens.append((syn, w, info))
				#tokens.extend(temp)
			else:
				print ("%s: %d" % (self.filename, self.lineno), u" 错误:",_codes[syn], w)
				return False
		return True
		
	def tokenParse(self, filename):
		''' 词法分析 '''
		tokens = []
		fp = None
		if is3X:
			fp = open(filename, encoding="utf-8")
		else:
			fp = open(filename)
		if not fp:
			print (u"找不到文件", filename)
			return tokens
		self.lineno = 1
		for line in fp:
			if not self.parseLine(line, tokens):
				break
			self.lineno = self.lineno + 1
		return tokens

	def getField(self, tokens, i, oldnumber):
		''' 解析出消息的域，返回 field, i '''
		isArray = False
		errormsg = tokens[i][2]
		if tokens[i][0] == _codes["KEY"]:
			if tokens[i][1] == _keyWorlds[3]:
				isArray = True
				i = i+1
			else:
				error(errormsg, u"使用了错误的域修饰符", tokens[i][1])
		checkSynNumber(i, self.n, 4, errormsg)
		checkKind(tokens[i], errormsg, self.basename)
		checkVarSyn(tokens[i+1], errormsg)
		checkSpc(tokens[i+2], "=", errormsg)
		checkSpc(tokens[i+3], "DIGIT", errormsg)
		checkEnd(tokens[i+4], tokens[i][2])
		checkFieldIndex(int(tokens[i+3][1]), oldnumber, errormsg)
		field = {"kind": tokens[i][1], "package": _MsgPackage.get(tokens[i][1], self.package), "id": tokens[i+1][1], "pos": tokens[i+3][1], "Kind": capitalize(tokens[i][1]), "Id": capitalize(tokens[i+1][1]), "array": isArray, "len": ""}
		if isTs:
			if field["kind"] == "bytes":
				field["len"] = "this." + field["id"] + ".length, "
		else:
			if field["kind"] == "bytes":
				field["len"] = "#self." + field["id"] + ", "
		return (field, i+4)
		
	def getMessage(self, tokens, i):
		''' 解析出消息定义，返回 msg, i '''
		token = tokens[i]
		checkKey(token, _keyWorlds[2], token[2])
		checkSynNumber(i, self.n, 3, token[2])
		checkVarSyn(tokens[i+1], token[2])
		msg = {"name": tokens[i+1][1], "protoid": 0, "fields": [], "group": 0, "filename": self.basename}
		_MsgFilename[msg["name"]] = self.basename
		errmsg = tokens[i+1][2]
		
		if tokens[i+2][0] == _codes["DIGIT"]:
			msg["protoid"] = tokens[i+2][1]
			i = i + 3
		else:
			i = i + 2
			
		checkSpc(tokens[i], "{", token[2])
		name = msg["name"]
		if has_key(_Messages, msg["name"]):
			error(u"重复的消息定义", msg["name"], errmsg, _Messages[msg["name"]])
		if int(msg["protoid"]) > 0:
			checkProtoId(msg["protoid"], errmsg)
		_Messages[msg["name"]] = errmsg
		_MsgPackage[msg["name"]] = self.package
		i = i + 1
		# 读取消息域
		oldnumber = 0
		while i < self.n and tokens[i][0] != _codes["}"]:
			(field, i) = self.getField(tokens, i, oldnumber)
			oldnumber = int(field["pos"])
			msg["fields"].append(field)
			i = i + 1
		checkSpc(tokens[i], "}", token[2])
		return (msg, i)

	def getGroup(self, tokens, i):
		''' 解析出分组 '''
		group = []
		token = tokens[i]
		checkKey(token, _keyWorlds[4], token[2])
		checkSynNumber(i, self.n, 3, token[2])
		checkSpc(tokens[i+1], "DIGIT", token[2])
		checkSpc(tokens[i+2], "{", token[2])
		protoid = tokens[i+1][1]
		checkProtoId(protoid, token[2])
		i = i + 3
		groupNumber = 1
		while i < self.n:
			if tokens[i][0] == _codes["_"]:
				checkSpc(tokens[i+1], ";", tokens[i][2])
				i = i + 2
				groupNumber = groupNumber + 1
			elif tokens[i][0] == _codes["KEY"] and tokens[i][1] == _keyWorlds[2]:
				(msg, i) = self.getMessage(tokens, i)
				msg["protoid"] = protoid
				msg["group"] = groupNumber
				group.append(msg)
				i = i + 1
				groupNumber = groupNumber + 1
			else:
				break
		checkSpc(tokens[i], "}", token[2])
		return (group, i)
	
	def syntaxParse(self, tokens):
		''' 语法分析 '''
		proto = None
		n = len(tokens)
		self.n = n
		self.tokens = tokens
		if n <= 2:
			return proto
		if tokens[0][0] == _codes["KEY"] and tokens[0][1] == _keyWorlds[0]:
			checkVarSyn(tokens[1], tokens[0][2])
			pk = tokens[1][1]
			self.package = tokens[1][1]
			proto = {"package" : tokens[1][1], "import" : [], "messages" : [], "importCount": 0}
			self.proto = proto
			i = 2
			while i < n:
				token = tokens[i]
				if token[0] == _codes["KEY"]:
					if token[1] == _keyWorlds[1]:		# import
						checkSynNumber(i, n, 2, token[2])
						checkPathSyn(tokens[i+1], token[2])
						proto["import"].append(tokens[i+1][1])
						importBaseName = tokens[i+1][1]
						importFileName = os.path.join(os.path.dirname(os.path.abspath(self.filename)), importBaseName + ".csbuf")
						if not os.path.isfile(importFileName):
							importFileName = os.path.join(_dirname, importBaseName + ".csbuf")
							if not os.path.isfile(importFileName):
								error(u"找不到 import 文件 %s" % importBaseName, tokens[i+1][2])
						p = Parser().parse(importFileName)
						if not p:
							error(tokens[i+1][2])
						i = i + 1
					elif token[1] == _keyWorlds[2]:		# 消息定义
						(msg, i) = self.getMessage(tokens, i)
						proto["messages"].append(msg)
					elif token[1] == _keyWorlds[4]:		# 分组消息
						(group, i) = self.getGroup(tokens, i)
						for msg in group:
							proto["messages"].append(msg)
					else:
						error(token[2], u"暂时不支持的关键字", token[1])
				elif token[0] == _codes[";"]:
					pass
				else:
					error(token[2], u"无法识别类型", token[1])
				i = i+1
		else:
			print (self.filename, u"没有定义 package")
		return proto
		
	def parse(self, filename):
		self.filename = filename
		self.basename = os.path.basename(filename)
		name = os.path.basename(filename)
		if not has_key(_Protos, name):
			tokens = self.tokenParse(filename)
			proto = self.syntaxParse(tokens)
			if proto:
				_Protos[name] = proto
				return proto
		else:
			return _Protos[name]
			



def output(format, proto, filename, outdir, ts_d_ts):
	''' 输出 lua 协议文件 '''
	bname = filename.split(".")[0]
	filename = filename.replace(".", "_")
	if not filename.endswith(format["suffix"]):
		filename = filename + format["suffix"]
	msgs = []
	for v in proto["messages"]:
		k = v["name"]
		fields = []
		fieldinits = []
		writes = []
		reads = []
		for f in v["fields"]:
			kind = f["kind"]
			f["filename"] = v["filename"].replace(".", "_")
			f["kindname"] = format["kind"].get(kind, kind)
			fkname = ""
			if kind in _MsgFilename:
				tn = _MsgFilename[kind].split(".")[0]
				if tn != bname:
					fkname = tn + "."
			f["filekind"] = fkname + f["kindname"] 	# 带文件名前缀的类型声明
			if _kindWorlds.count(kind) <= 0:	# 为 go 语言添加结构体的指针类型
				f["kindnamep"] = "*" + f["kindname"]
			else:
				f["kindnamep"] = f["kindname"]
			f["defvalue"] = format["defvalue"].get(kind, 0)
			if f["array"]:	# 数组
				fields.append(format["arrayfield"] % f)
				if has_key(_Messages, kind):
					writes.append(format["arraymessagewrite"] % f)
					reads.append(format["arraymessageread"] % f)
					importname = _MsgFilename[kind].split(".")[0]
					if importname != bname and proto["import"].count(importname) == 0:
						proto["import"].append(importname)
				else:
					writes.append(format["arraywrite"] % f)
					reads.append(format["arrayread"] % f)
			elif has_key(_Messages, kind):	# 消息结构体
				fields.append(format["messagefield"] % f)
				writes.append(format["messagewrite"] % f)
				reads.append(format["messageread"] % f)
				importname = _MsgFilename[kind].split(".")[0]
				if importname != bname and proto["import"].count(importname) == 0:
					proto["import"].append(importname)
			else:
				fields.append(format["field"] % f)
				writes.append(format["write"] % f)
				reads.append(format["read"] % f)
				fieldinits.append(format["fieldinit"] % f)
		if len(v["fields"]) == 0 and format == format_py:
			msgs.append(format["message"] % {"package": proto["package"], "message": k, "Message": capitalize(k), "protoid": v["protoid"],
			"field": "\tpass", "fieldinit": "", "write": "\tpass", "read": "\tpass"})
		else:
			msgs.append(format["message"] % {"package": proto["package"], "message": k, "Message": capitalize(k), "protoid": v["protoid"],
			"field": "\n\t".join(fields), "fieldinit": "\n\t".join(fieldinits), "write": "\n\t".join(writes), "read": "\n\t".join(reads)})
			ts_d_ts.append(format_ts_d_ts["message"] % {"package": proto["package"], "message": k, "Message": capitalize(k), "protoid": v["protoid"],
			"field": "\n\t".join(fields), "fieldinit": "\n\t".join(fieldinits), "write": "\n\t".join(writes), "read": "\n\t".join(reads)})
	ip = ""
	for i in proto["import"]:
		ip += format["import"] % {"name": i, "package": proto["package"]}
	fn = os.path.basename(filename)
	filestr = format["head"] % {"package": proto["package"], "body": "\n".join(msgs), "filename": fn, "import":ip, "defname": fn.replace(".", "_").upper()}
	#print filestr
	filestr = filestr.encode("utf-8")
	saveFile(os.path.join(outdir, filename), filestr)
		
def main():
	if len(sys.argv) <= 1:
		usage()
		return
		
	input_file = sys.argv[1]
	aditon_input_file = [input_file]
	output_file = os.getcwd()
	opts, args = getopt.getopt(sys.argv[2:], "hclgtpo:i:")
	outformats = []
	global isGo
	global isLua
	global isTs
	global isPy
	for op, value in opts:
		if op == "-i":
			aditon_input_file.append(value)
		elif op == "-o":
			output_file = value
		elif op == "-c":
			outformats.append(format_cpp)
		elif op == "-l":
			outformats.append(format_lua)
			isLua = True
		elif op == "-g":
			outformats.append(format_go)
			isGo = True
		elif op == "-t":
			outformats.append(format_ts)
			isTs = True
		elif op == "-p":
			outformats.append(format_py)
			isPy = True
		elif op == "-h":
			usage()
	
	try:
		while True:
			if len(outformats) <= 0:
				print (u"请定义输出语言 -c c++ -l lua -g go")
				break
			
			for infile in aditon_input_file:
				if not infile:
					print (u"没有输入文件")
					return
					
				if not os.path.exists(infile):
					print (u"输入文件", infile, u"不存在")
					return
					
				if os.path.isfile(infile):
					_dirname = os.path.dirname(os.path.abspath(infile))
					Parser().parse(infile)
				elif os.path.isdir(infile):
					_dirname = infile
					files = os.listdir(infile)
					p = Parser()
					for f in files:
						fpath = os.path.join(infile, f)
						if f.endswith("csbuf"):
							p.parse(fpath)
			
			checkKinds()
			creater = []
			luaprotos = []
			tsprotos = []
			pyprotos = []
			ts_d_ts = []
			pk = ""
			groupNumber = 0
			if not os.path.exists(output_file):
				os.mkdir(output_file)
			for k, v in _Protos.items():
				#if isGo:
				pk = v["package"]
				for msg in v["messages"]:
					if int(msg["protoid"]) > 0:
						d = {"package": v["package"], "message": msg["name"], "Message": capitalize(msg["name"]), "protoid": msg["protoid"], "group": msg["group"], "filename": msg["filename"].split(".")[0]}
						creater.append("csbuf.RegisterCreater(%(protoid)s, New%(Message)s, %(group)s)" % d)
						
						luaprotos.append("_protocols[%(group)s][%(protoid)s] = %(package)s.%(message)s" % d)
						tsprotos.append("_protocols[%(group)s][%(protoid)s] = %(filename)s.%(message)s;" % d)
						pyprotos.append("_protocols[%(group)s][%(protoid)s] = %(message)s" % d)
					
				for format in outformats:
					output(format, v, k, output_file, ts_d_ts)
				
			if isGo:
				filestr = "package " + pk
				filestr += '\nimport "gitee.com/wint/go-csbuf/csbuf"'
				filestr += "\n\nfunc RegisterCreater() {\n\t"
				filestr += "\n\t".join(creater)
				filestr += "\n}\n"
				saveFile(os.path.join(output_file, "csbuf.go"), filestr)
			if isLua:	
				headers = [[v["importCount"], k] for k, v in _Protos.items()]
				headers.sort()
				headers.reverse()
				luaheader = []
				for _, k in headers:
					luaheader.append('require ("%s.%s")' % (v["package"], k.replace(".", "_")))
				saveFile(os.path.join(output_file, "protocol.lua"), include_lua % {"filename": "\n".join(luaheader), "protos": "\n".join(luaprotos)})
			if isTs:
				headers = [[v["importCount"], k] for k, v in _Protos.items()]
				headers.sort()
				headers.reverse()
				tsheader = []
				tsexports = []
				pk = "ptl"
				for _, k in headers:
					tsheader.append('import * as %s from "./%s";' % (k.split(".")[0], k.replace(".", "_")))
					tsexports.append('export * from "./%s";' % (k.replace(".", "_")))
					pk = v["package"]
				saveFile(os.path.join(output_file, "protocol.ts"), include_ts % {"filename": "\n".join(tsheader), "protos": "\n".join(tsprotos), "export": "\n".join(tsexports)})
				filestr = "import * as %(package)s from \"./protocol\";\nwindow[\"%(package)s\"] = %(package)s;" % {"package": pk}
				saveFile(os.path.join(output_file, "protocolNs.ts"), filestr)
				filestr = "declare namespace %s {\n%s\n}" % (pk, "\n".join(ts_d_ts))
				filestr = re.sub(" = .*;", ";", filestr)
				filestr = re.sub(": .*\.", ": ", filestr)
				saveFile(os.path.join(output_file, "../../../%(package)s.d.ts" % {"package": pk}), filestr)
			if isPy:
				headers = [[v["importCount"], k] for k, v in _Protos.items()]
				headers.sort()
				headers.reverse()
				pyheader = []
				for _, k in headers:
					pyheader.append('from %s.%s import *' % (v["package"], k.replace(".", "_")))
				saveFile(os.path.join(output_file, "__init__.py"), include_py % {"filename": "\n".join(pyheader), "protos": "\n".join(pyprotos)})
				
			break
	except:
		traceback.print_exc()
	#raw_input("")

def saveFile(filename, filestr):
	fp = None
	if is3X:
		fp = open(filename, "a+", encoding="utf-8")
	else:
		fp = open(filename, "a+")
	if fp:
		fp.seek(0)
		fstr = fp.read()
		if fstr != filestr:
			fp.seek(0)
			fp.truncate(0)
			if is3X:
				try:
					filestr = filestr.decode("utf-8")
				except:
					pass
				fp.write(filestr)
			else:
				fp.write(filestr)
		fp.close()

# lua 格式化输出字符串
format_lua = {
"head" : u'\n-- 本文件由自动工具 proto.py 自动生成，请勿修改\n-- filename %(filename)s\n-- package %(package)s\n\n%(import)s\n%(package)s = %(package)s or {}\n\n%(body)s\nreturn %(package)s\n',
"import" : '',
"field" : '%(id)s = %(defvalue)s,',
"arrayfield" : '%(id)s = {},',
"messagefield" : '%(id)s = %(package)s.%(kind)s:create(),',
"fieldinit" : '',
"write" : 'writer:set%(Kind)s(self.%(id)s, %(len)s%(pos)s)',
"arraywrite" : 'writer:setArray(#self.%(id)s, %(pos)s)\n\tfor _, v in ipairs(self.%(id)s) do\n\t\twriter:set%(Kind)s(v, 0)\n\tend',
"arraymessagewrite" : 'writer:setArray(#self.%(id)s, %(pos)s)\n\tfor _, v in ipairs(self.%(id)s) do\n\t\twriter:setMessage(0)\n\tv:Marshal(writer)\n\twriter:endSetMessage()\n\tend',
"messagewrite" : 'writer:setMessage(%(pos)s)\n\tself.%(id)s:Marshal(writer)\n\twriter:endSetMessage()',
"read" : 'self.%(id)s = reader:get%(Kind)s(%(pos)s)',
"arrayread" : 'local count = reader:getArray(%(pos)s)\n\tfor i=1, count do\n\t\tself.%(id)s[i] = reader:get%(Kind)s(0)\n\tend',
"arraymessageread" : 'local count = reader:getArray(%(pos)s)\n\tfor i=1, count do\n\t\tlocal msg = %(package)s.%(kind)s:create()\n\t\treader:getMessage(0)\n\tmsg:Unmarshal(reader)\n\treader:endGetMessage()\n\t\tself.%(id)s[i] = msg\n\tend',
"messageread" : 'reader:getMessage(%(pos)s)\n\tself.%(id)s:Unmarshal(reader)\n\treader:endGetMessage()',
"message" : u'-- message %(message)s\n%(package)s.%(message)s = {\n\tprotoid = %(protoid)s,\n\t%(field)s\n}\n\nfunction %(package)s.%(message)s:Marshal(writer)\n\t%(write)s\nend\n\nfunction %(package)s.%(message)s:Unmarshal(reader)\n\t%(read)s\nend\n\nfunction %(package)s.%(message)s:create()\n\treturn clone(%(package)s.%(message)s)\nend\n',
"suffix" : ".lua",
"kind" : {},	# 定义成员变量时所用的类型名
"defvalue" : {"bool": "false", "string": '""', "bytes": '""'},	# 默认值，没有的话默认为 0
}

# c++ 格式化输出字符串
format_cpp = {
"head" : u'#ifndef __%(defname)s__\n#define __%(defname)s__\n// 本文件由自动工具 proto.py 自动生成，请勿修改\n// filename %(filename)s\n// package %(package)s\n\n#include "CsbufParser.h"\n#include "CsbufMessage.h"\n%(import)s\nnamespace %(package)s {\n\n%(body)s\n}\n#endif\n',
"import" : '#include "%(name)s_csbuf.h"\n',
"field" : '%(kindname)s %(id)s;',
"arrayfield" : 'std::vector<%(kindname)s> %(id)s;',
"messagefield" : '%(kindname)s %(id)s;',
"fieldinit" : '\t%(id)s = %(defvalue)s;',
"write" : '\twriter.set%(Kind)s(%(id)s, %(pos)s);',
"arraywrite" : '\tauto count%(id)s = (uint16_t)%(id)s.size();\n\t\twriter.setArray(count%(id)s, %(pos)s);\n\t\tfor (uint16_t i=0; i<count%(id)s; ++i)\n\t\t{\n\t\t\twriter.set%(Kind)s(%(id)s[i], 0);\n\t\t}',
"arraymessagewrite" : '\tauto count%(id)s = (uint16_t)%(id)s.size();\n\t\twriter.setArray(count%(id)s, %(pos)s);\n\t\tfor (uint16_t i=0; i<count%(id)s; ++i)\n\t\t{\n\t\t\twriter.setMessage(0);\n\t\t\t%(id)s[i].Marshal(writer);\n\t\t\twriter.endSetMessage();\n\t\t}',
"messagewrite" : '\twriter.setMessage(%(pos)s);\n\t\t%(id)s.Marshal(writer);\n\t\twriter.endSetMessage();',
"read" : '\t%(id)s = reader.get%(Kind)s(%(pos)s);',
"arrayread" : '\tauto count%(id)s = reader.getArray(%(pos)s);\n\t\tfor (uint16_t i=0; i<count%(id)s; ++i)\n\t\t{\n\t\t\t%(id)s.push_back(reader.get%(Kind)s(0));\n\t\t}',
"arraymessageread" : '\tauto count%(id)s = reader.getArray(%(pos)s);\n\t\tfor (uint16_t i=0; i<count%(id)s; ++i)\n\t\t{\n\t\t\t%(kind)s msg;\n\t\t\treader.getMessage(0);\n\t\t\tmsg.Unmarshal(reader);\n\t\t\treader.endGetMessage();\n\t\t\t%(id)s.push_back(msg);\n\t\t}',
"messageread" : '\treader.getMessage(%(pos)s);\n\t\t%(id)s.Unmarshal(reader);\n\t\treader.endGetMessage();',
"message" : u'// message %(message)s\nclass %(message)s: public CsbufMessage\n{\npublic:\n\t%(field)s\n\n\t%(message)s()\n\t: CsbufMessage(%(protoid)s)\n\t{\n\t%(fieldinit)s\n\t}\n\n\tvoid Marshal(CsbufParser& writer)\n\t{\n\t%(write)s\n\t}\n\n\tvoid Unmarshal(CsbufParser& reader)\n\t{\n\t%(read)s\n\t}\n};',
"suffix" : ".h",
"kind" : {"string": "std::string", "bytes": "std::string", "uint8": "uint8_t", "uint16": "uint16_t", "uint32": "uint32_t", "uint64": "uint64_t", "int8": "int8_t", "int16": "int16_t", "int32": "int32_t", "int64": "int64_t"},
"defvalue" : {"bool": "false", "string": '""', "bytes": '""'},
}
			
# go 格式化输出字符串
format_go = {
"head" : u'// 本文件由自动工具 proto.py 自动生成，请勿修改\n// filename %(filename)s\n// package %(package)s\n\npackage %(package)s\n\nimport (\n\t"gitee.com/wint/go-csbuf/csbuf"\n)\n\n%(body)s\n',
"import" : '\t"%(name)s"\n',
"field" : '%(Id)s %(kindnamep)s `json:"%(id)s"`',
"arrayfield" : '%(Id)s []%(kindnamep)s',
"messagefield" : '%(Id)s %(kindnamep)s',
"fieldinit" : '',
"write" : 'w.Set%(Kind)s(m.%(Id)s, %(pos)s)',
"arraywrite" : 'w.SetArray(uint16(len(m.%(Id)s)), %(pos)s)\n\tfor _, v := range m.%(Id)s {\n\t\tw.Set%(Kind)s(v, 0)\n\t}',
"arraymessagewrite" : 'w.SetArray(uint16(len(m.%(Id)s)), %(pos)s)\n\tfor _, v := range m.%(Id)s {\n\t\tw.SetMessage(0)\n\t\tv.Marshal(w)\n\t\tw.EndSetMessage()\n\t}',
"messagewrite" : 'w.SetMessage(%(pos)s)\n\tif m.%(Id)s != nil {\n\t\tm.%(Id)s.Marshal(w)\n\t}\n\tw.EndSetMessage()',
"read" : 'm.%(Id)s = r.Get%(Kind)s(%(pos)s)',
"arrayread" : 'count%(Id)s := int(r.GetArray(%(pos)s))\n\tfor i := 0; i < count%(Id)s; i++ {\n\t\tm.%(Id)s = append(m.%(Id)s, r.Get%(Kind)s(0))\n\t}',
"arraymessageread" : 'count%(Id)s := int(r.GetArray(%(pos)s))\n\tfor i := 0; i < count%(Id)s; i++ {\n\t\tmsg := new(%(Kind)s)\n\t\tr.GetMessage(0)\n\t\tmsg.Unmarshal(r)\n\t\tr.EndGetMessage()\n\t\tm.%(Id)s = append(m.%(Id)s, msg)\n\t}',
"messageread" : 'r.GetMessage(%(pos)s)\n\tif m.%(Id)s == nil {\n\t\tm.%(Id)s = new(%(Kind)s)\n\t}\n\tm.%(Id)s.Unmarshal(r)\n\tr.EndGetMessage()',
"message" : u'// message %(message)s\ntype %(Message)s struct {\n\t%(field)s\n}\n\nfunc (m *%(Message)s) GetProtoId() uint16 {\n\treturn %(protoid)s\n}\n\nfunc (m *%(Message)s) Marshal(w *csbuf.CsbufParser) {\n\t%(write)s\n}\n\nfunc (m *%(Message)s) Unmarshal(r *csbuf.CsbufParser) {\n\t%(read)s\n}\n\nfunc New%(Message)s() csbuf.CsbufMessage {\n\treturn &%(Message)s{}\n}\n',
"suffix" : ".go",
"kind" : {"float": "float32", "double": "double64", "bytes": "string"},	# 定义成员变量时所用的类型名
"defvalue" : {"bool": "false", "string": '""', "bytes": '""'},	# 默认值，没有的话默认为 0
}

# typescript 格式化输出字符串
format_ts = {
"head" : u'// 本文件由自动工具 proto.py 自动生成，请勿修改\n// filename %(filename)s\n// package %(package)s\n\n%(import)s\n%(body)s\n',
"import" : 'import * as %(name)s from "./%(name)s_csbuf"\n',
"field" : '%(id)s: %(kindname)s = %(defvalue)s;',
"arrayfield" : '%(id)s: %(filekind)s[] = [];',
"messagefield" : '%(id)s: %(filekind)s = new %(filekind)s();',
"fieldinit" : '',
"write" : 'writer.set%(Kind)s(this.%(id)s, %(len)s%(pos)s)',
"arraywrite" : 'writer.setArray(this.%(id)s.length, %(pos)s)\n\tfor (let v of this.%(id)s) {\n\t\twriter.set%(Kind)s(v, 0)\n\t}',
"arraymessagewrite" : 'writer.setArray(this.%(id)s.length, %(pos)s)\n\tfor (let v of this.%(id)s) {\n\t\twriter.setMessage(0)\n\t\tv.Marshal(writer)\n\t\twriter.endSetMessage()\n\t}',
"messagewrite" : 'writer.setMessage(%(pos)s)\n\tthis.%(id)s.Marshal(writer)\n\twriter.endSetMessage()',
"read" : 'this.%(id)s = reader.get%(Kind)s(%(pos)s)',
"arrayread" : 'let count%(Id)s = reader.getArray(%(pos)s)\n\tfor (let i=0; i < count%(Id)s; i++) {\n\t\tthis.%(id)s.push(reader.get%(Kind)s(0))\n\t}',
"arraymessageread" : 'let count%(Id)s = reader.getArray(%(pos)s)\n\tfor (let i=0; i < count%(Id)s; i++) {\n\t\tlet msg = new %(filekind)s()\n\t\treader.getMessage(0)\n\t\tmsg.Unmarshal(reader)\n\t\treader.endGetMessage()\n\t\tthis.%(id)s.push(msg)\n\t}',
"messageread" : 'reader.getMessage(%(pos)s)\n\tthis.%(id)s.Unmarshal(reader)\n\treader.endGetMessage()',
"message" : u'// message %(message)s\nexport class %(message)s {\n\tprotoid: number = %(protoid)s;\n\t%(field)s\n\n\nMarshal(writer: any) {\n\t%(write)s\n}\n\nUnmarshal(reader: any) {\n\t%(read)s\n}\n}\n',
"suffix" : ".ts",
"kind" : {"float": "number", "double": "number", "bytes": "number[]", "uint8": "number", "uint16": "number", "uint32": "number", "uint64": "number", "int8": "number", "int16": "number", "int32": "number", "int64": "number", "bool": "boolean"},	# 定义成员变量时所用的类型名
"defvalue" : {"bool": "false", "string": '""', "bytes": '[]'},	# 默认值，没有的话默认为 0
}

# tyupescript 格式化声明文件
format_ts_d_ts = {
"message" : u'export class %(message)s {\n\tprotoid: number;\n\t%(field)s\n}\n',
}

# python 格式化输出字符串
format_py = {
"head" : u'#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#coding=utf8\n\n# 本文件由自动工具 proto.py 自动生成，请勿修改\n# filename %(filename)s\n# package %(package)s\n\n%(import)s\n\n%(body)s\n',
"import" : 'from %(package)s.%(name)s_csbuf import *\n',
"field" : '\tself.%(id)s = %(defvalue)s',
"arrayfield" : '\tself.%(id)s = []',
"messagefield" : '\tself.%(id)s = %(kindname)s()',
"fieldinit" : '',
"write" : '\twriter.set%(Kind)s(self.%(id)s, %(pos)s)',
"arraywrite" : '\tcount%(id)s = len(self.%(id)s)\n\t\twriter.setArray(count%(id)s, %(pos)s)\n\t\tfor i in range(count%(id)s):\n\t\t\twriter.set%(Kind)s(self.%(id)s[i], 0)',
"arraymessagewrite" : '\tcount%(id)s = len(self.%(id)s)\n\t\twriter.setArray(count%(id)s, %(pos)s)\n\t\tfor i in range(count%(id)s):\n\t\t\twriter.setMessage(0)\n\t\t\tself.%(id)s[i].Marshal(writer)\n\t\t\twriter.endSetMessage()',
"messagewrite" : '\twriter.setMessage(%(pos)s)\n\t\tself.%(id)s.Marshal(writer)\n\t\twriter.endSetMessage()',
"read" : '\tself.%(id)s = reader.get%(Kind)s(%(pos)s)',
"arrayread" : '\tfor i in range(reader.getArray(%(pos)s)):\n\t\t\tself.%(id)s.append(reader.get%(Kind)s(0))',
"arraymessageread" : '\tfor i in range(reader.getArray(%(pos)s)):\n\t\t\tmsg = %(kind)s() \n\t\t\treader.getMessage(0)\n\t\t\tmsg.Unmarshal(reader)\n\t\t\treader.endGetMessage()\n\t\t\tself.%(id)s.append(msg)',
"messageread" : '\treader.getMessage(%(pos)s)\n\t\tself.%(id)s.Unmarshal(reader)\n\t\treader.endGetMessage()',
"message" : u'# message %(message)s\nclass %(message)s:\n\tdef __init__(self):\n\t\tself.protoid = %(protoid)s\n\t%(field)s\n\n\tdef Marshal(self, writer):\n\t%(write)s\n\n\tdef Unmarshal(self, reader):\n\t%(read)s',
"suffix" : ".py",
"kind" : {},
"defvalue" : {"False": "false", "string": '""', "bytes": '""'},
}

include_lua = '''%(filename)s

local _protocols = {[0]={}, {}, {}, {}, {}, {}, {}, {}, {}, {}}

%(protos)s

function getProtocol(protoId, groupNumber)
	if groupNumber ~= nil and groupNumber > 0 then
		return _protocols[groupNumber][protoId] or _protocols[0][protoId]
	end
	return _protocols[0][protoId]
end
'''

include_ts = '''%(filename)s

let _p1: {[key: number]: any;} = {};
let _p2: {[key: number]: any;} = {};
let _p3: {[key: number]: any;} = {};
let _p4: {[key: number]: any;} = {};
let _protocols = [_p1, _p2, _p3, _p4];

%(protos)s

export function getProtocol(protoId, groupNumber) {
	if (groupNumber != undefined && groupNumber > 0) {
		let ret = _protocols[groupNumber][protoId]
		if (ret != undefined) {
			return ret
		}
	}
	return _protocols[0][protoId]
}

%(export)s
'''

include_py = '''#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#coding=utf8
# 协议模块定义文件

%(filename)s

_protocols = [{}, {}, {}, {}];

%(protos)s

def getProtocol(protoId, groupNumber):
	if groupNumber != None and groupNumber > 0:
		ret = _protocols[groupNumber].get(protoId)
		if ret != None:
			return ret
		
	return _protocols[0].get(protoId)

def protoDict(proto):
	d = {}
	for k, v in proto.__dict__.items():
		if isinstance(v, (int, str, float)):
			d[k] = v
		elif isinstance(v, list):
			d[k] = []
			for p in v:
				if isinstance(p, (int, str, float)):
					d[k].append(p)
				else:
					d[k].append(protoDict(p))
		else:
			d[k] = protoDict(v)
	return d
'''

if __name__ == "__main__":
	main()